package com.share.common.mbatis.plugins;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;

import java.util.*;

/**
 * https://blog.csdn.net/qq_28898309/article/details/106812466
 * service和Controller的生成
 *
 * @author 01401061 - 孙艳强
 * @date 2020/10/28 18:21
 */
@Slf4j
public class ServiceGeneratorPlugin extends PluginAdapter {
    private static VarItem AUTOWIRED = new VarItem("@Autowired", "org.springframework.beans.factory.annotation.Autowired");
    private static VarItem RESOURCE = new VarItem("@Resource", "javax.annotation.Resource");
    private static VarItem INTEGERR_ESULT = new VarItem("Integer", "java.lang.Integer");
    private static VarItem LONG_RESULT = new VarItem("Long", "java.lang.Long");
    private static VarItem LIST_RESULT = new VarItem("List", "java.util.List");
    private static VarItem int_result = new VarItem("int", "int");
    private static VarItem long_result = new VarItem("long", "long");
    private static VarItem SERVICE = new VarItem("@Service", "org.springframework.stereotype.Service");
    private static VarItem SLF4J = new VarItem("@Slf4j", "lombok.extern.slf4j.Slf4j");

    // 项目目录，一般为 src/main/java
    private String targetProject;

    // service包名，如：com.example.service
    private String servicePackage;

    // service实现类包名，如：com.example.service.impl
    private String serviceImplPackage;

    // service接口名后缀 如""
    private String servicePreffix;

    // service接口名后缀 如Service
    private String serviceSuffix;

    @Override
    public boolean validate(List<String> warnings) {
        boolean valid = true;
        this.targetProject = Optional.ofNullable(properties.getProperty("targetProject")).orElse("src/main/java");
        this.servicePackage = Optional.ofNullable(properties.getProperty("servicePackage")).orElse("com.example.service");
        this.serviceImplPackage = Optional.ofNullable(properties.getProperty("serviceImplPackage")).orElse("com.example.service.impl");
        this.servicePreffix = Optional.ofNullable(properties.getProperty("servicePreffix")).orElse("");
        this.serviceSuffix = Optional.ofNullable(properties.getProperty("serviceSuffix")).orElse("Service");
        log.info("自定义插件配置获取成功!");

        String suppressGenerator = properties.getProperty("suppressGenerator");/*service生成配置的开关*/
        if("true".equals(suppressGenerator)){
            valid =  true;
        }
        return  valid;
    }

    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        /*生成对应的文件*/
        List<GeneratedJavaFile> fileList = new ArrayList<>();

        /*model类名(无后缀)*/
        String packageModelName = introspectedTable.getBaseRecordType();
        String modelName = packageModelName.substring(packageModelName.lastIndexOf(".") + 1);
        VarItem modelItem = new VarItem(modelName, packageModelName);

        /*modelExample类名(无后缀)*/
        VarItem modelExampleItem = new VarItem(modelItem.getName() + "Example", modelItem.getPath() + "Example");

        /*modelmapper类名(无后缀)*/
        String packageMapperName = introspectedTable.getMyBatis3JavaMapperType();
        String mapperName = packageMapperName.substring(packageMapperName.lastIndexOf(".") + 1);
        VarItem mapperItem = new VarItem(mapperName, packageMapperName);

        /*主键信息---对于多主键的我们跳过*/
        VarItem primaryKeyItem = null;
        List<IntrospectedColumn> primaryKeyColumns = introspectedTable.getPrimaryKeyColumns();
        if (primaryKeyColumns != null && primaryKeyColumns.size() == 1) {
            IntrospectedColumn introspectedColumn = primaryKeyColumns.get(0);
            String javaProperty = introspectedColumn.getJavaProperty();
            String fullyQualifiedName = introspectedColumn.getFullyQualifiedJavaType().getFullyQualifiedName();
            primaryKeyItem = new VarItem(javaProperty, fullyQualifiedName);
        }

        /*包名.类名(无后缀)*/
        String packageServiceName = this.servicePackage + "." + this.servicePreffix + modelName + this.serviceSuffix;
        String packageServiceImplName = this.serviceImplPackage + "." + modelName + this.serviceSuffix + "Impl";

        /****************************************开始组装************************************************/

        /*生成接口类*/
        log.info("正在配置生成接口XxxService");
        FullyQualifiedJavaType servicePath = new FullyQualifiedJavaType(packageServiceName);
        Interface service = new Interface(servicePath);
        service.setVisibility(JavaVisibility.PUBLIC);
        fileList.add(new GeneratedJavaFile(service, this.targetProject, context.getJavaFormatter()));

        /*生成实现类*/
        log.info("正在配置生成XxxServiceImpl实现类");
        FullyQualifiedJavaType serviceImpl = new FullyQualifiedJavaType(packageServiceImplName);
        TopLevelClass clazz = new TopLevelClass(serviceImpl);
        //描述类的作用域修饰符
        clazz.setVisibility(JavaVisibility.PUBLIC);

        //描述类 的实现接口类
        clazz.addSuperInterface(servicePath);

        //描述类 引入的类
        clazz.addImportedType(servicePath);/*需要import的类*/
        clazz.addImportedType(new FullyQualifiedJavaType(SERVICE.getPath()));
        clazz.addImportedType(new FullyQualifiedJavaType(SLF4J.getPath()));

        //添加的注解
        clazz.addAnnotation(SLF4J.getName());
        clazz.addAnnotation(SERVICE.getName());

        log.info("正在为XxxServiceImpl添加XxxMapper属性");
        addField(clazz, mapperItem, RESOURCE);

        log.info("正在为XxxServiceImpl添加方法");
        addServiceAndImplMethods(clazz, service, primaryKeyItem, mapperItem, modelItem, modelExampleItem);

        fileList.add(new GeneratedJavaFile(clazz, this.targetProject, context.getJavaFormatter()));
        return fileList;
    }

    private void addServiceAndImplMethods(TopLevelClass clazz, Interface service, VarItem primaryKeyItem, VarItem mapperItem, VarItem modelItem, VarItem modelExampleItem) {
        /*添加属性*/
        List<VarItem> params = new LinkedList<>();

        log.info("正在为XxxServiceImpl添加方法-countByExample");
        params.clear();
        params.add(modelExampleItem);
        addMethodWithParamObject(clazz, service, "countByExample", long_result, mapperItem, params);


        log.info("正在为XxxServiceImpl添加方法-selectByExample");
        params.clear();
        params.add(modelExampleItem);
        addMethodWithParamObject(clazz, service, "selectByExample", LIST_RESULT, mapperItem, params);


        log.info("正在为XxxServiceImpl添加-deleteByExample");
        params.clear();
        params.add(modelExampleItem);
        addMethodWithParamObject(clazz, service, "deleteByExample", int_result, mapperItem, params);


        log.info("正在为XxxServiceImpl添加-insert");
        params.clear();
        params.add(modelItem);
        addMethodWithParamObject(clazz, service, "insert", int_result, mapperItem, params);


        log.info("正在为XxxServiceImpl添加-insertSelective");
        params.clear();
        params.add(modelItem);
        addMethodWithParamObject(clazz, service, "insertSelective", int_result, mapperItem, params);

        log.info("正在为XxxServiceImpl添加-updateByExampleSelective");
        params.clear();
        params.add(modelItem);
        params.add(modelExampleItem);
        addMethodWithParamObject(clazz, service, "updateByExampleSelective", int_result, mapperItem, params);

        log.info("正在为XxxServiceImpl添加-updateByExample");
        params.clear();
        params.add(modelItem);
        params.add(modelExampleItem);
        addMethodWithParamObject(clazz, service, "updateByExample", int_result, mapperItem, params);

        log.info("正在为XxxServiceImpl添加-updateByPrimaryKeySelective");
        params.clear();
        params.add(modelItem);
        addMethodWithParamObject(clazz, service, "updateByPrimaryKeySelective", int_result, mapperItem, params);

        log.info("正在为XxxServiceImpl添加-updateByPrimaryKey");
        params.clear();
        params.add(modelItem);
        addMethodWithParamObject(clazz, service, "updateByPrimaryKey", int_result, mapperItem, params);

        /*查询条件是主键---有一定的问题*/
        if (primaryKeyItem != null) {
            params.clear();
            params.add(primaryKeyItem);
            addMethodWithParamObject(clazz, service, "deleteByPrimaryKey", int_result, mapperItem, params);

            params.clear();
            params.add(primaryKeyItem);
            addMethodWithParamObject(clazz, service, "selectByPrimaryKey", modelItem, mapperItem, params);
        }
    }


    private void addField(TopLevelClass clazz, VarItem mapperItem, VarItem autowire) {
        //描述类的成员属性
        String fieldName = firstCharToLowCase(mapperItem.getName());
        Field daoField = new Field(fieldName, new FullyQualifiedJavaType(mapperItem.getPath()));
        //设置导包
        clazz.addImportedType(new FullyQualifiedJavaType(autowire.getPath()));
        clazz.addImportedType(new FullyQualifiedJavaType(mapperItem.getPath()));
        //描述成员属性 的注解
        daoField.addAnnotation(autowire.getName());
        //描述成员属性修饰符
        daoField.setVisibility(JavaVisibility.PRIVATE);
        clazz.addField(daoField);
    }

    private void addMethodWithParamObject(TopLevelClass clazz, Interface service, String methodName, VarItem result, VarItem mapperItem, List<VarItem> params) {
        Map<String, Object> serviceMap = genMethodService(methodName, result, mapperItem, params);
        service.setVisibility(JavaVisibility.PUBLIC);
        Method serviceMethod = (Method) serviceMap.get("method");
        service.addMethod(serviceMethod);
        serviceMethod.setAbstract(true);/*设置不需要方法体的方法*/
        service.addImportedType(new FullyQualifiedJavaType(LIST_RESULT.getPath()));

        Map<String, Object> serviceImplMap = genMethodService(methodName, result, mapperItem, params);
        Method serviceImplMethod = (Method) serviceImplMap.get("method");
        String body = serviceImplMap.get("body").toString();

        //方法注解
        serviceImplMethod.addAnnotation("@Override");
        //方法体，逻辑代码
        String strBody = firstCharToLowCase(mapperItem.getName()) + "." + methodName + body;
        serviceImplMethod.addBodyLine("return " + strBody + ";");

        clazz.addMethod(serviceImplMethod);
        clazz.setVisibility(JavaVisibility.PUBLIC);
        clazz.addImportedType(new FullyQualifiedJavaType(LIST_RESULT.getPath()));

        for (VarItem param : params) {
            service.addImportedType(new FullyQualifiedJavaType(param.getPath()));
            clazz.addImportedType(new FullyQualifiedJavaType(param.getPath()));
        }
    }

    private Map<String, Object> genMethodService(String methodName, VarItem result, VarItem mapperItem, List<VarItem> params) {
        Map<String, Object> map = new HashMap<>();

        //描述 方法名
        Method method = new Method(methodName);
        StringBuffer sb = new StringBuffer();
        sb.append("(");
        int index = 0;
        for (VarItem param : params) {
            FullyQualifiedJavaType paramType = new FullyQualifiedJavaType(param.getPath());
            String paramName = null;
            if (param.getPath().startsWith("java.")) {/*来自jdk的基础数据类型*/
                paramName = param.getName();
            } else if (param.getName().endsWith("Example")) {/*这里的角标有坑---必须顺序填写---此处采用约定*/
                paramName = "example";
            } else {
                paramName = "record";
            }
            method.addParameter(index, new Parameter(paramType, paramName));
            sb.append(paramName + ",");

            index++;
        }

        /*删除最后一个逗号*/
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append(")");
        log.info("方法参数: " + sb.toString());

        FullyQualifiedJavaType retureType = new FullyQualifiedJavaType(result.getPath());
        if ("selectByExample".equals(methodName)) {
            String modelPath = method.getParameters().get(0).getType().getFullyQualifiedName().replace("Example", "");
            retureType.addTypeArgument(new FullyQualifiedJavaType(modelPath));
        }
        //返回值
        method.setReturnType(retureType);

        //修饰符
        method.setVisibility(JavaVisibility.PUBLIC);

        map.put("method", method);
        map.put("body", sb.toString());
        return map;
    }

    private String firstCharToLowCase(String str) {
        char[] chars = new char[1];
        chars[0] = str.charAt(0);
        String temp = new String(chars);
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            return str.replaceFirst(temp, temp.toLowerCase());
        }
        return str;
    }
}

@Data
class VarItem {
    private String name;
    private String path;

    public VarItem(String name, String path) {
        this.name = name;
        this.path = path;
    }
}